using System;
using System.Linq;
using Castle.DynamicProxy;
using System.Collections.Generic;
namespace Duhking
{
    public class InterfaceDuhker : IDuhker
    {
        private static readonly ProxyGenerator _generator = new ProxyGenerator();
        private Stack<Type> _typeStack = new Stack<Type>();
        private Dictionary<object, object> _proxyCache = new Dictionary<object,object>();

        public bool CanDuhk<T>(object target)
        {
            return CanDuhk(typeof(T),target.GetType());
        }

        public bool CanDuhk(Type interfaceType, Type objectType)
        {
            if(!(interfaceType.IsInterface))
                throw new InvalidOperationException(string.Format("Unable to Duhk type {0} because it is not an interface.  Duhking of non-interface types is currently not supported",interfaceType));
            return TargetMatchesAllMethods(interfaceType,objectType);
        }

        public T Duhk<T>(object target) where T: class
        {
            return Duhk(typeof(T), target) as T;
        }

        public object Duhk(Type interfaceType, object target)
        {
            if (_proxyCache.ContainsKey(target))
                return _proxyCache[target];
            
            if(!CanDuhk(interfaceType,target.GetType()))
                throw new InvalidOperationException(string.Format("Unable to Duhk type {0} using concrete type {1}", interfaceType, target.GetType().Name));

            _proxyCache.Add(target,_generator.CreateInterfaceProxyWithoutTarget(interfaceType, new DuhkingInterceptor(target,this)));
            return _proxyCache[target];
        }

        private bool TargetMatchesAllMethods(Type interfaceType, Type targetType)
        {
            if (_typeStack.Contains(targetType))
                return true;
            _typeStack.Push(targetType);
            var interfaceMethods = interfaceType.GetMethods();
            var targetMethods = targetType.GetMethods();
            MethodInfoComparer comparer = new MethodInfoComparer(this);
            try
            {
                return interfaceMethods.All(m => targetMethods.Any(t => comparer.Equals(m, t)));
            }
            finally
            {
                _typeStack.Pop();
            }
        }
    }
}